# Predicate Bytecode
This note covers `src/scoring/backends/predicate/*`.
## End-to-end pipeline
The predicate backend is implemented as a compiler plus VM.
1. source text enters as `ScorerSpec::Predicate("...")`,
2. [[#Parser]] turns text into [[#Expr]],
3. `typecheck.rs` resolves names against [[Modules/Schema#Schema]] and emits [[#Op]] values,
4. those ops are stored in a [[#Program]],
5. `vm.rs` executes the program against a [[Modules/Location State#LocationView]].
## `Expr`
**Defined in:** `ast.rs`
The AST enum for predicate expressions.
### Variants
- `Num(f64)`
- `Slot { kind: String, attr: String }`
- `Neg(Box<Expr>)`
- `Not(Box<Expr>)`
- `Bin(BinOp, Box<Expr>, Box<Expr>)`
- `Call(String, Vec<Expr>)`
### Role
This is the purely syntactic representation before slot references become byte offsets.
## `BinOp`
**Defined in:** `ast.rs`
The binary operator enum.
### Variants
- `Add`
- `Sub`
- `Mul`
- `Div`
- `Lt`
- `Le`
- `Gt`
- `Ge`
- `Eq`
- `Ne`
- `And`
- `Or`
## `Parser<'a>`
**Defined in:** `parser.rs`
A handwritten recursive-descent parser over the predicate DSL.
### Public API
- `Parser::new(src)`
- `parse()`
### Why handwritten parsing is reasonable here
The language is small and expression-shaped. A custom parser keeps the grammar readable and avoids parser-framework overhead.
### Supported constructs
- numeric literals,
- slot refs like `$audience.male_frac`,
- arithmetic,
- comparisons,
- logical operations,
- builtin calls such as `min`, `max`, `abs`.
## `Op`
**Defined in:** `bytecode.rs`
The VM opcode enum.
### Literal and load ops
- `PushF32(f32)`
- `LoadI64 { offset }`
- `LoadF32 { offset }`
- `LoadF64 { offset }`
- `LoadU32 { offset }`
### Unary ops
- `Neg`
- `Not`
- `Abs`
### Binary arithmetic ops
- `Add`
- `Sub`
- `Mul`
- `Div`
### Comparison ops
- `Lt`
- `Le`
- `Gt`
- `Ge`
- `Eq`
- `Ne`
### Logical ops
- `And`
- `Or`
### Aggregate-style ops
- `MinA`
- `MaxA`
### Semantics
The VM stack is `f32` only. Booleans are represented as `0.0` and `1.0`.
## `Program`
**Defined in:** `bytecode.rs`
The compiled executable predicate.
### Fields
- `ops: Vec<Op>`
- `max_stack: usize`
### Role
`max_stack` lets the VM pre-size its stack with minimal overhead.
## `PredicateBuilder`
**Defined in:** `mod.rs`
The backend-specific implementation of [[Modules/Scoring Core#ScorerBuilder]].
### Role
Validates that the incoming [[Modules/Scoring Core#ScorerSpec]] is a predicate spec, then performs parse + compile.
## `PredicateScorer`
**Defined in:** `mod.rs`
A small wrapper around a compiled [[#Program]].
### Role
Implements the [[Modules/Scoring Core#Scorer]] trait by delegating to the VM.
## Compilation semantics in `typecheck.rs`
Although the file is named `typecheck.rs`, it performs both validation and code generation.
### What it validates
- kind names exist,
- attribute names exist,
- resolved slots are of supported scalar types,
- builtin names and arities are valid.
### What it rejects
Array-valued slots are rejected for predicate scoring in the current implementation.
### What it emits
A linear sequence of [[#Op]] values plus stack-depth bookkeeping.
## VM semantics in `vm.rs`
### Stack representation
`SmallVec<[f32; 16]>`
### Important behaviors
- division by zero yields `0.0`,
- unbalanced programs yield `0.0` instead of panicking,
- `Load*` instructions read by raw offset from [[Modules/Location State#LocationView]].
### Critical architectural fact
The VM never resolves names at runtime. The schema lookup cost has already been paid at compile time.
## Why this subsystem matters
This backend is the best illustration of the crate's design philosophy:
- low-level storage,
- explicit compilation,
- predictable execution,
- minimal trigger-time branching.
It is also the subsystem used in the audience-matching scenario documented in [[Scenarios/Audience Bytecode Matching]].